import 'package:string_scanner/string_scanner.dart';
import 'package:string_scanner/src/charcode.dart';

import '../common/test_page.dart';

class StringScannerTestPage extends TestPage {
  StringScannerTestPage(super.title) {
    group('with an empty string', () {

      test('StringScanner('').isDone, StringScanner('').expectDone', () {
        StringScanner scanner = StringScanner('');
        expect(scanner.isDone, 'isTrue');
        expect(scanner.expectDone, '');
      });

      test('StringScanner('').rest', () {
        StringScanner scanner = StringScanner('');
        expect(scanner.rest, 'isEmpty');
      });

      test('StringScanner('').lastMatch', () {
        StringScanner scanner = StringScanner('');
        expect(scanner.lastMatch, 'isNull');
      });

      test('StringScanner('').position', () {
        StringScanner scanner = StringScanner('');
        expect(scanner.position, 0);
      });

      test("StringScanner('').readChar", () {
        StringScanner scanner = StringScanner('');
        expect(scanner.readChar, '');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test("StringScanner('').readCodePoint", () {
        StringScanner scanner = StringScanner('');
        expect(scanner.readCodePoint, '');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test("StringScanner('').peekChar()", () {
        StringScanner scanner = StringScanner('');
        expect(scanner.peekChar(), 'isNull');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test("StringScanner('').peekCodePoint()", () {
        StringScanner scanner = StringScanner('');
        expect(scanner.peekCodePoint(), 'isNull');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test("StringScanner('').scanChar(\$f)", () {
        StringScanner scanner = StringScanner('');
        expect(scanner.scanChar($f), 'isFalse');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test("StringScanner('')expectChar(\$f)", () {
        StringScanner scanner = StringScanner('');
        expect(() => scanner.expectChar($f), '');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test("StringScanner('').scan(RegExp('.'))", () {
        StringScanner scanner = StringScanner('');
        expect(scanner.scan(RegExp('.')), 'isFalse');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test("StringScanner('').expect(RegExp('.'))", () {
        StringScanner scanner = StringScanner('');
        expect(() => scanner.expect(RegExp('.')), '');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test("StringScanner('').matches(RegExp('.'))", () {
        StringScanner scanner = StringScanner('');
        expect(scanner.matches(RegExp('.')), 'isFalse');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test('StringScanner('').substring(0)', () {
        StringScanner scanner = StringScanner('');
        expect(scanner.substring(0), 'isEmpty');
      });

      test('StringScanner('').position = 1', () {
        StringScanner scanner = StringScanner('');
        expect(() {
          scanner.position = 1;
        }, '');
      });

      test('StringScanner('').position = -1', () {
        StringScanner scanner = StringScanner('');
        expect(() {
          scanner.position = -1;
        }, '');
      });
    });

    group('at the beginning of a string', () {

      test('StringScanner("foo bar").isDone', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.isDone, 'isFalse');
        expect(scanner.expectDone, '');
      });

      test('StringScanner("foo bar").rest', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.rest, 'foo bar');
      });

      test('StringScanner("foo bar").lastMatch.lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.lastMatch, 'isNull');
      });

      test('StringScanner("foo bar").position', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.position, 0);
      });

      test('StringScanner("foo bar").readChar().lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.readChar(), 0x66);
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 1);
      });

      test('StringScanner("foo bar").readCodePoint().lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.readCodePoint(), 0x66);
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 1);
      });

      test('StringScanner("foo bar").peekChar().lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.peekChar(), 0x66);
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test('StringScanner("foo bar").peekChar(4).lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.peekChar(4), 0x62);
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test('StringScanner("foo bar").peekCodePoint().lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.peekCodePoint(), 0x66);
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test('StringScanner("foo bar").scanChar(\$f).lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scanChar($f), 'isTrue');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 1);
      });

      test('StringScanner("foo bar").scanChar(\$x).lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scanChar($x), 'isFalse');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test('StringScanner("foo bar").expectChar(\$f).lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        scanner.expectChar($f);
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 1);
      });

      test('StringScanner("foo bar").expectChar(\$x).lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(() => scanner.expectChar($x), '');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
      });

      test('StringScanner("foo bar").scan(RegExp("f(..)")).lastMatch[1]', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan(RegExp('f(..)')), 'isTrue');
        expect(scanner.lastMatch![1], 'oo');
        expect(scanner.position, 3);
        expect(scanner.rest, ' bar');
      });

      test('StringScanner("foo bar").matches(RegExp("f(..)")).lastMatch 和 .scan(RegExp("b(..)")).lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.matches(RegExp('f(..)')), 'isTrue');
        expect(scanner.lastMatch, 'isNotNull');

        expect(scanner.scan(RegExp('b(..)')), 'isFalse');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
        expect(scanner.rest, 'foo bar');
      });

      test('StringScanner("foo bar").expect(RegExp("f(..)")).lastMatch[1]', () {
        StringScanner scanner = StringScanner('foo bar');
        scanner.expect(RegExp('f(..)'));
        expect(scanner.lastMatch![1], 'oo');
        expect(scanner.position, (3));
        expect(scanner.rest, ' bar');
      });

      test('StringScanner("foo bar").matches(RegExp("f(..)")).lastMatch 和 .expect(RegExp("b(..)")).lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.matches(RegExp('f(..)')), 'isTrue');
        expect(scanner.lastMatch, 'isNotNull');

        expect(() => scanner.expect(RegExp('b(..)')), '');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 0);
        expect(scanner.rest, 'foo bar');
      });

      test('StringScanner("foo bar").matches(RegExp("f(..)")).lastMatch![1]', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.matches(RegExp('f(..)')), 'isTrue');
        expect(scanner.lastMatch![1], 'oo');
        expect(scanner.position, 0);
        expect(scanner.rest, 'foo bar');
      });

      test("不匹配的匹配项返回false并且不更改状态",
              () {
            StringScanner scanner = StringScanner('foo bar');
            expect(scanner.matches(RegExp('b(..)')), 'isFalse');
            expect(scanner.lastMatch, 'isNull');
            expect(scanner.position, 0);
            expect(scanner.rest, 'foo bar');
          });

      test('从开始的子字符串返回空字符串', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.substring(0), 'isEmpty');
      });

      test('带有自定义结尾的子字符串返回子字符串', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.substring(0, 3), 'foo');
      });

      test('字符串长度为的子字符串返回整个字符串', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.substring(0, 7), 'foo bar');
      });

      test('将位置设置为1将光标向前移动', () {
        StringScanner scanner = StringScanner('foo bar');
        scanner.position = 1;
        expect(scanner.position, 1);
        expect(scanner.rest, 'oo bar');

        expect(scanner.scan(RegExp('oo.')), 'isTrue');
        expect(scanner.lastMatch![0], 'oo ');
        expect(scanner.position, 4);
        expect(scanner.rest, 'bar');
      });

      test('设置字符串之外的位置会引发ArgumentError', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(() {
          scanner.position = 8;
        }, '');
      });

      test('将位置设置为-1将引发ArgumentError', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(() {
          scanner.position = -1;
        }, '');
      });

      test('扫描接受任何模式', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo'), 'isTrue');
        expect(scanner.lastMatch![0], 'foo');
        expect(scanner.position, 3);
        expect(scanner.rest, ' bar');
      });

      test('多次扫描', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan(RegExp('f(..)')), 'isTrue');
        expect(scanner.lastMatch![1], 'oo');
        expect(scanner.position, 3);
        expect(scanner.rest, ' bar');

        expect(scanner.scan(RegExp(' b(..)')), 'isTrue');
        expect(scanner.lastMatch![1], 'ar');
        expect(scanner.position, 7);
        expect(scanner.rest, '');
        expect(scanner.isDone, 'isTrue');
        expect(scanner.expectDone, '');
      });
    });

    group('after a scan', () {

      test('readChar返回第一个字符并取消设置最后一个匹配项', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo'), 'isTrue');
        expect(scanner.readChar(), $space);
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 4);
      });

      test('readCodePoint返回第一个字符并取消设置最后一个匹配项',
              () {
                StringScanner scanner = StringScanner('foo bar');
                expect(scanner.scan('foo'), 'isTrue');
            expect(scanner.readCodePoint(), $space);
            expect(scanner.lastMatch, 'isNull');
            expect(scanner.position, 4);
          });

      test('匹配的scanChar返回true并取消设置最后一个匹配', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo'), 'isTrue');
        expect(scanner.scanChar($space), 'isTrue');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 4);
      });

      test('匹配的expectChar返回true并取消设置最后一个匹配', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo'), 'isTrue');
        scanner.expectChar($space);
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 4);
      });
    });

    group('at the end of a string', () {

      test('完成', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.isDone, 'isTrue');
        expect(scanner.expectDone, '');
      });

      test('其余部分为空', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.rest, 'isEmpty');
      });

      test('位置为零', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.position, 7);
      });

      test("readChar失败并且不改变状态", () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.readChar, '');
        expect(scanner.lastMatch, 'isNotNull');
        expect(scanner.position, 7);
      });

      test("readCodePoint失败并且不更改状态", () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.readCodePoint, '');
        expect(scanner.lastMatch, 'isNotNull');
        expect(scanner.position, 7);
      });

      test("peekChar返回null并且不更改状态", () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.peekChar(), 'isNull');
        expect(scanner.lastMatch, 'isNotNull');
        expect(scanner.position, 7);
      });

      test("peekCodePoint返回null并且不更改状态", () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.peekCodePoint(), 'isNull');
        expect(scanner.lastMatch, 'isNotNull');
        expect(scanner.position, 7);
      });

      test("scanChar返回false并且不更改状态", () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.scanChar($f), 'isFalse');
        expect(scanner.lastMatch, 'isNotNull');
        expect(scanner.position, 7);
      });

      test("expectChar失败并且不改变状态", () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(() => scanner.expectChar($f), '');
        expect(scanner.lastMatch, 'isNotNull');
        expect(scanner.position, 7);
      });

      test('scan返回false并将lastMatch设置为null', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.scan(RegExp('.')), 'isFalse');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 7);
      });

      test('expect抛出FormatException并将lastMatch设置为null', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(() => scanner.expect(RegExp('.')), '');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 7);
      });

      test('matches返回false，将lastMatch设置为null', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.matches(RegExp('.')), 'isFalse');
        expect(scanner.lastMatch, 'isNull');
        expect(scanner.position, 7);
      });

      test('从开始的子字符串返回整个字符串', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.substring(0), 'foo bar');
      });

      test('具有自定义开头的子字符串从中返回一个子字符串', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.substring(4), 'bar');
      });

      test('具有自定义开始和结束的子字符串返回该子字符串', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(scanner.substring(3, 5), ' b');
      });

      test('将位置设置为1将光标向后移动', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        scanner.position = 1;
        expect(scanner.position, 1);
        expect(scanner.rest, 'oo bar');

        expect(scanner.scan(RegExp('oo.')), 'isTrue');
        expect(scanner.lastMatch![0], 'oo ');
        expect(scanner.position, 4);
        expect(scanner.rest, 'bar');
      });

      test('设置和重置位置清除lastMatch', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        final oldPosition = scanner.position;
        scanner.position = 1;
        scanner.position = oldPosition;
        expect(scanner.lastMatch, 'isNull');
      });

      test('设置字符串之外的位置会引发ArgumentError', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(() {
          scanner.position = 8;
        }, '');
      });

      test('将位置设置为-1将引发ArgumentError', () {
        StringScanner scanner = StringScanner('foo bar');
        expect(scanner.scan('foo bar'), 'isTrue');
        expect(() {
          scanner.position = -1;
        }, '');
      });
    });

    group('before a surrogate pair', () {
      final codePoint = '\uD83D\uDC6D'.runes.first;
      const highSurrogate = 0xD83D;

      test('readChar返回高代理项并移动到对中', () {
        StringScanner scanner = StringScanner('foo: \uD83D\uDC6D');
        expect(scanner.scan('foo: '), 'isTrue');
        expect(scanner.readChar(), (highSurrogate));
        expect(scanner.position, 6);
      });

      test('readCodePoint返回代码单位并移动经过该对', () {
        StringScanner scanner = StringScanner('foo: \uD83D\uDC6D');
        expect(scanner.scan('foo: '), 'isTrue');
        expect(scanner.readCodePoint(), codePoint);
        expect(scanner.position, 7);
      });

      test('peekChar返回高代理', () {
        StringScanner scanner = StringScanner('foo: \uD83D\uDC6D');
        expect(scanner.scan('foo: '), 'isTrue');
        expect(scanner.peekChar(), highSurrogate);
        expect(scanner.position, 5);
      });

      test('peekCodePoint返回代码单元', () {
        StringScanner scanner = StringScanner('foo: \uD83D\uDC6D');
        expect(scanner.scan('foo: '), 'isTrue');
        expect(scanner.peekCodePoint(), codePoint);
        expect(scanner.position, 5);
      });

      test('具有高代理项的scanChar移动到对中', () {
        StringScanner scanner = StringScanner('foo: \uD83D\uDC6D');
        expect(scanner.scan('foo: '), 'isTrue');
        expect(scanner.scanChar(highSurrogate), 'isTrue');
        expect(scanner.position, 6);
      });

      test('带有代码点的scanChar移过该对', () {
        StringScanner scanner = StringScanner('foo: \uD83D\uDC6D');
        expect(scanner.scan('foo: '), 'isTrue');
        expect(scanner.scanChar(codePoint), 'isTrue');
        expect(scanner.position, 7);
      });

      test('具有高代理项的expectChar移动到对中', () {
        StringScanner scanner = StringScanner('foo: \uD83D\uDC6D');
        expect(scanner.scan('foo: '), 'isTrue');
        scanner.expectChar(highSurrogate);
        expect(scanner.position, 6);
      });

      test('带有代码点的expectChar移过该对', () {
        StringScanner scanner = StringScanner('foo: \uD83D\uDC6D');
        expect(scanner.scan('foo: '), 'isTrue');
        scanner.expectChar(codePoint);
        expect(scanner.position, 7);
      });
    });

    group('before an invalid surrogate pair', () {
      // This surrogate pair is invalid because U+E000 is just outside the range
      // of low surrogates. If it were interpreted as a surrogate pair anyway, the
      // value would be U+110000, which is outside of the Unicode gamut.
      const codePoint = 0x110000;
      const highSurrogate = 0xD800;

      test('readChar返回高代理项并移动到对中', () {
        StringScanner scanner = StringScanner('foo: \uD800\uE000');
        expect(scanner.scan('foo: '), 'isTrue');
        expect(scanner.readChar(), highSurrogate);
        expect(scanner.position, 6);
      });

      test('readCodePoint返回高代理项并移过该对',
              () {
            StringScanner scanner = StringScanner('foo: \uD800\uE000');
            expect(scanner.scan('foo: '), 'isTrue');
            expect(scanner.readCodePoint(), highSurrogate);
            expect(scanner.position, 6);
          });

      test('peekChar返回高代理', () {
        StringScanner scanner = StringScanner('foo: \uD800\uE000');
        expect(scanner.scan('foo: '), 'isTrue');
        expect(scanner.peekChar(), highSurrogate);
        expect(scanner.position, 5);
      });

      test('peekCodePoint返回高代理', () {
        StringScanner scanner = StringScanner('foo: \uD800\uE000');
        expect(scanner.scan('foo: '), 'isTrue');
        expect(scanner.peekCodePoint(), highSurrogate);
        expect(scanner.position, 5);
      });

      test('具有高代理项的scanChar移动到对中', () {
        StringScanner scanner = StringScanner('foo: \uD800\uE000');
        expect(scanner.scan('foo: '), 'isTrue');
        expect(scanner.scanChar(highSurrogate), 'isTrue');
        expect(scanner.position, 6);
      });

      test('带有伪代码点的scanChar返回false', () {
        StringScanner scanner = StringScanner('foo: \uD800\uE000');
        expect(scanner.scan('foo: '), 'isTrue');
        expect(scanner.scanChar(codePoint), 'isFalse');
        expect(scanner.position, 5);
      });

      test('具有高代理项的expectChar移动到对中', () {
        StringScanner scanner = StringScanner('foo: \uD800\uE000');
        expect(scanner.scan('foo: '), 'isTrue');
        scanner.expectChar(highSurrogate);
        expect(scanner.position, 6);
      });

      test('带有伪代码点的expectChar失败', () {
        StringScanner scanner = StringScanner('foo: \uD800\uE000');
        expect(scanner.scan('foo: '), 'isTrue');
        expect(() => scanner.expectChar(codePoint), '');
      });
    });

    group('a scanner constructed with a custom position', () {
      test('从该位置开始扫描', () {
        final scanner = StringScanner('foo bar', position: 1);
        expect(scanner.position, (1));
        expect(scanner.rest, 'oo bar');

        expect(scanner.scan(RegExp('oo.')), 'isTrue');
        expect(scanner.lastMatch![0], 'oo ');
        expect(scanner.position, 4);
        expect(scanner.rest, 'bar');
      });

      test('如果位置为-1，则引发ArgumentError', () {
        expect(() => StringScanner('foo bar', position: -1), '');
      });

      test('如果位置超出字符串，则引发ArgumentError', () {
        expect(() => StringScanner('foo bar', position: 8), '');
      });
    });
  }

}